#ifndef __MY_SERVICE__
#define __MY_SERVICE__

#include <ctime>
#include "data.hpp"
#include "httplib.h"

extern cloud::DataManager *_data; // 引入一个全局的DataManager指针，对数据进行管理

namespace cloud
{
    class Service
    {
    private:
        int _server_port;
        std::string _server_ip;
        std::string _download_prefix;
        httplib::Server _server;

    private:
        // 统一使用静态成员函数，因为httplib库对某一请求方法的路径（url）对应的处理函数是静态的，路由处理器
        // _server.Post("/upload", Upload);当服务器收到指向/upload路径的POST请求时，将调用Upload函数来处理这个请求
        // 上传文件请求处理函数
        static void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            // post /upload 文件数据在请求正文中
            auto ret = req.has_file("file"); // 判断表单中有没有上传的文件区域，html把表单中的name统一设置为file，有file则有文件
            if (ret == false)
            {
                rsp.status = 400;
                return;
            }
            // 返回MultipartFormData结构体，通过结构体获取文件内容与信息
            const auto &file = req.get_file_value("file");
            // file.filename 文件名称  file.content 文件数据
            std::string back_dir = Config::GetInstance()->GetBackDir();
            std::string realpath = back_dir + FileUtil(file.filename).FileName(); // 匿名对象
            FileUtil fu(realpath);
            fu.SetContent(file.content); // 将数据写入文件中
            BackupInfo info;
            info.NewBackupInfo(realpath); // 为上传文件设置备份信息
            _data->Insert(info);          // 向数据管理模块（哈希表）添加备份的文件信息
            return;
        }

        // 将时间戳转化为格式为"ddd mmm dd hh:mm:ss yyyy"的字符串
        static std::string TimetoStr(time_t t)
        {
            std::string tmp = std::ctime(&t);
            return tmp;
        }

        // 备份文件列表展示请求处理函数
        static void Listshow(const httplib::Request &req, httplib::Response &rsp)
        {
            // 获取所有的文件备份信息
            std::vector<BackupInfo> arry;
            _data->GetAll(&arry);
            // 根据所有备份信息，组织html文件数据
            std::stringstream ss; // 字符串流相比字符串更加灵活，不用频繁的使用+拼接
            ss << "<html><head><title>Download</title></head>";
            ss << "<body><h1>Download</h1><table>";
            for (auto &a : arry)
            {
                ss << "<tr>"; // 行
                std::string filename = FileUtil(a.real_path).FileName();
                ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>"; // 一行的一个单元，这里创建一个超链接，'a.url'为超链接，filename为链接文本
                ss << "<td align='right'>" << TimetoStr(a.mtime) << "</td>";       // align='right'为水平方向右对齐
                ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";
                ss << "</tr>";
            }
            ss << "</table></body></html>";
            rsp.body = ss.str();
            rsp.set_header("Content-Type", "text/html; charset=UTF-8"); // 浏览器根据正文类型对报文进行解析，例如html就需要渲染生成前端页面
            // rsp.status = 200;
            return;
        }

        static std::string GetETag(const BackupInfo &info)
        {
            // etg :  filename-fsize-mtime 唯一标识资源的当前版本
            // 当资源发生变化时，服务器会生成一个新的ETag值，客户端可以使用这个值来判断缓存中的资源是否是最新的
            FileUtil fu(info.real_path);
            std::string etag = fu.FileName();
            etag += "-";
            // 这里使用哈希表中的备份文件信息，因为解压缩文件也是对文件的修改，实际上备份文件并没有改变
            etag += std::to_string(info.fsize);
            etag += "-";
            etag += std::to_string(info.mtime);
            return etag;
        }

        static void Download(const httplib::Request &req, httplib::Response &rsp)
        {
            // 获取客户端请求的资源路径path req.path
            // 根据资源路径，获取文件备份信息
            BackupInfo info;
            _data->GetOneByURL(req.path, &info);
            // 判断文件是否被压缩，如果被压缩，要先解压缩
            if (info.pack_flag == true)
            {
                FileUtil fu(info.pack_path);
                fu.UnCompress(info.real_path); // 将文件解压到备份目录下
                // 删除压缩包，修改备份信息
                fu.Remove();
                info.pack_flag = false;
                _data->Update(info);
            }

            bool retrans = false;
            std::string old_etag;
            if (req.has_header("If-Range")) // 请求部分资源字段
            {
                old_etag = req.get_header_value("If-Range");
                // 有If-Range字段且，这个字段的值与请求文件的最新etag一致则符合断点续传
                if (old_etag == GetETag(info))
                {
                    retrans = true;
                }
            }

            // 读取文件数据，放入响应报文的正文部分
            FileUtil fu(info.real_path);
            if (retrans == false)
            {
                fu.GetContent(&rsp.body);
                // 5.设置响应头部字段：ETag，Accept-Ranges：bytes
                rsp.set_header("Accept-Ranges", "bytes"); // 服务器支持按字节范围请求数据
                rsp.set_header("ETag", GetETag(info));
                rsp.set_header("Content-Type", "application/octet-stream"); // 二进制数据类型，通常用于传输未知类型的文件
                rsp.status = 200;
            }
            else
            {
                // httplib内部实现了对于区间请求也就是断点续传请求的处理
                // 只需要我们用户将文件所有数据读取到rsp.body中，它内部会自动根据请求
                // 区间，从body中取出指定区间数据进行响应
                //  std::string  range = req.get_header_val("Range"); bytes=start-end
                fu.GetContent(&rsp.body);
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("ETag", GetETag(info));
                rsp.set_header("Content-Type", "application/octet-stream");
                // rsp.set_header("Content-Range", "bytes start-end/fsize");
                rsp.status = 206; // 区间请求响应的是206*****
            }
        }

    public:
        Service()
        {
            Config *config = Config::GetInstance();
            _server_port = config->GetServerPort();
            _server_ip = config->GetServerIp();
            _download_prefix = config->GetDownloadPrefix();
        }

        // 服务端的运行函数，本是上设置路由处理器，其他靠httplib库完成
        bool RunModule()
        {
            // 对指定路由的某一请求方法进行处理
            _server.Post("/Upload", Upload);
            _server.Get("/listshow", Listshow);
            _server.Get("/", Listshow);
            std::string download_url = _download_prefix + "(.*)"; // 正则表达式匹配任意字符串
            _server.Get(download_url, Download);
            printf("%s:%d\n", _server_ip.c_str(), _server_port);
            _server.listen(_server_ip, _server_port);
            return true;
        }
    };
}

#endif
